package kassiopeia.agent.seda.config;

import kassiopeia.agent.plugin.ContextImpl;
import kassiopeia.agent.plugin.PluginFactory;
import kassiopeia.agent.seda.stage.Controller;
import kassiopeia.agent.seda.event.EventQueue;
import kassiopeia.agent.seda.stage.Stage;
import kassiopeia.agent.seda.threads.PoolSizeStrategy;
import kassiopeia.commons.exception.SedaException;
import kassiopeia.commons.model.task.Connection;
import kassiopeia.commons.model.task.ParameterInstance;
import kassiopeia.commons.model.task.PluginInstance;
import kassiopeia.commons.model.task.TaskDescriptor;
import kassiopeia.plugin.Plugin;
import kassiopeia.plugin.PluginException;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@Component
public class TaskConfigurator {
    private Logger log = Logger.getLogger(TaskConfigurator.class);

    @Autowired
    private PluginFactory pluginFactory;
    @Autowired
    private PoolSizeStrategy poolSizeStrategy;

    public ConfiguredTask configure(TaskDescriptor task) {
        log.info("Configuring task...");
        Map<String, Stage> stages = createStages(task);
        List<SedaConnection> connections = createConnections(task, stages);
        log.info("Task configuration done");
        return new ConfiguredTask(new LinkedList<Stage>(stages.values()), connections, task);
    }

    private Map<String, Stage> createStages(TaskDescriptor task) {
        log.info("Creating stages...");
        HashMap<String, Stage> stages = new HashMap<String, Stage>();
        for (PluginInstance plugin : task.getPlugins()) {
            String pluginInstanceId = plugin.getInstanceId();
            if (stages.containsKey(pluginInstanceId)) {
                throw new SedaException("Duplicated stage for plugin: " + pluginInstanceId);
            }
            try {
                Stage stage = createStageForPlugin(plugin);
                stages.put(pluginInstanceId, stage);
                log.info("Stage for plugin: " + pluginInstanceId + " created");
            } catch (PluginException px) {
                throw new SedaException("Cannot create stage for plugin: " + pluginInstanceId, px);
            }
        }
        return stages;
    }

    private Stage createStageForPlugin(PluginInstance pluginInstance) {
        Plugin plugin = createAndConfigurePlugin(pluginInstance);
        EventQueue eventQueue = new EventQueue();
        Controller controller = new Controller(eventQueue, pluginInstance.getInstanceId());
        return new Stage(plugin, eventQueue, controller);
    }

    private Plugin createAndConfigurePlugin(PluginInstance pluginInstance) {
        Plugin plugin = pluginFactory.newInstance(pluginInstance.getCoordinates(), pluginInstance.getInstanceId());

        if(pluginInstance.getConfiguration() != null) {
            configurePluginParameters(pluginInstance, plugin);
        }

        return plugin;
    }

    private void configurePluginParameters(PluginInstance pluginInstance, Plugin plugin) {
        String instanceId = pluginInstance.getInstanceId();
        ContextImpl context = (ContextImpl)plugin.getContext();
        for(ParameterInstance parameter : pluginInstance.getConfiguration()) {
            String key = parameter.getName();
            String value = parameter.getValue();
            context.putParameter(key, value);

            log.debug("Setting parameter '" + key + "' for plugin '" + instanceId + "'");
        }
    }

    private List<SedaConnection> createConnections(TaskDescriptor task, Map<String, Stage> stages) {
        log.info("Creating connections...");
        List<SedaConnection> connections = new LinkedList<SedaConnection>();
        for (Connection connection : task.getConnections()) {
            String toPlugin = connection.getToPlugin();
            Stage stage = stages.get(toPlugin);
            if (stage == null) {
                throw new SedaException("No such plugin: " + toPlugin);
            }
            connections.add(new SedaConnection(connection, stage.getEventQueue()));
            log.info("Connection created: " + connection);
        }
        return connections;
    }
}
